﻿#include "FITKGraphObjectLoad_P.h"

// VTK
#include <vtkArrowSource.h>
#include <vtkPoints.h>
#include <vtkVertex.h>
#include <vtkCellArray.h>
#include <vtkPolyData.h>
#include <vtkPointData.h>
#include <vtkCellData.h>
#include <vtkDoubleArray.h>
#include <vtkGlyph3D.h>
#include <vtkDistanceToCamera.h>
#include <vtkMaskPoints.h>
#include <vtkActor.h>
#include <vtkProperty.h>
#include <vtkTransform.h>
#include <vtkPolyDataNormals.h>
#include <vtkAppendPolyData.h>
#include <vtkCellCenters.h>
#include <vtkPolygon.h>
#include <vtkAppendFilter.h>
#include <vtkExtractSelection.h>
#include <vtkIdTypeArray.h>
#include <vtkUnstructuredGrid.h>
#include <vtkSelectionNode.h>

// Graph
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"
#include "FITKGraphCommons.h"

// Global data
#include "FITK_Kernel/FITKCore/FITKDataRepo.h"

// Data
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaLoadPressure.h"
#include "FITK_Component/FITKAbaqusData/FITKAbaqusPart.h"
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaMeshPartVTK.h"
#include "FITK_Interface/FITKInterfaceModel/FITKAssInstanceM4.h"
#include "FITK_Interface/FITKInterfaceModel/FITKAssInstanceTR.h"
#include "FITK_Component/FITKAbaqusData/FITKAbaModelInfo.h"
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaAssembly.h"
#include "FITK_Interface/FITKInterfaceModel/FITKModelSet.h"
#include "FITK_Interface/FITKInterfaceModel/FITKMeshSurfaceElement.h"
#include "FITK_Interface/FITKInterfaceModel/FITKAbstractElement.h"
#include "FITK_Component/FITKAbaqusData/FITKAbaModelInfo.h"
#include "FITK_Kernel/FITKCore/FITKVec3D.h"
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaSurfaceLabel.h"

// Data manager
#include "FITKSetSurfPolyProvider.h"

namespace Exchange
{
    FITKGraphObjectLoad_P::FITKGraphObjectLoad_P(AbaqusData::FITKDataCase* caseData, Interface::FITKAbaLoadPressure* pressureData, int stepId)
        : FITKGraphObjectLoadBase(caseData, pressureData, stepId)
    {
        if (!pressureData)
        {
            return;
        }

        // Generate the load's surface poly data and the cell center of surface.
        bool flag = generatePolyData(pressureData);
        if (!flag)
        {
            return;
        }

        // Generate load actors.
        generateGraphs();

        // Clear cache.
        // clearCache();

        // Save the graph information.
        m_graphInfo.Type = OtherGraph;

        // Update the visibility.
        updateVisibility();
    }

    FITKGraphObjectLoad_P::~FITKGraphObjectLoad_P()
    {
        clearCache();
    }

    void FITKGraphObjectLoad_P::update(bool forceUpdate)
    {
        // Update the visibility first of all.
        updateVisibility();

        // Change the pressure data value will not affect the graph,
        // but while the surface has been changed, the graph must be re-created,
        // so the pressure load doesn't need to check if the step has been changed.
        Interface::FITKAbaLoadPressure* pressureData = dynamic_cast<Interface::FITKAbaLoadPressure*>(_dataObj);
        if (!m_polyData || !pressureData)
        {
            return;
        }

        int surfaceId = pressureData->getComponentID();

        // Check if the surface is deleted.
        Interface::FITKMeshSurface* surfaceData = Core::FITKDataRepo::getInstance()->getTDataByID<Interface::FITKMeshSurface>(surfaceId);
        if (!surfaceData)
        {
            // Clear data.
            clearData();
            return;
        }

        // Check if the surface changed.
        if (surfaceId == m_surfaceId && !forceUpdate)
        {
            return;
        }

        // Clear data first.
        clearData();

        // Re-generate the poly data.
        bool flag = generatePolyData(pressureData);
        if (!flag)
        {
            return;
        }

        // Because the poly data has been reset, we need to set the normal again.
        // m_polyData->GetPointData()->SetNormals(m_normal);

        // Modify actors.
        updateActors();
    }

    void FITKGraphObjectLoad_P::updateVisibility()
    {
        // Update the full actor's visibility.
        FITKGraphObject3D::updateVisibility();

        // Update the extractor's ids.
        if (!m_idArray || !m_modelIdArray)
        {
            return;
        }

        QHash<int, bool> modelVisibilityHash;

        // Save the visibility first, and check if the visbility has changed at the same time.
        bool hasChanged = false;
        for (int modelId : m_modelIds)
        {
            bool parentVisible = getParentDataVisibility(modelId);
            bool oldVisibility = false;
            if (m_modelVisibilityHash.contains(modelId))
            {
                oldVisibility = m_modelVisibilityHash[modelId];
            }

            if (parentVisible != oldVisibility)
            {
                hasChanged = true;
            }

            modelVisibilityHash.insert(modelId, parentVisible);
        }

        // If the visibility hasn't changed, then return.
        if (!hasChanged && !m_modelVisibilityHash.isEmpty())
        {
            return;
        }

        // Reset the extractor's ids.
        resetVtkObj(m_idArray);

        // Update the extractor's ids.
        for (int i = 0; i < m_modelIdArray->GetNumberOfValues(); i++)
        {
            int modelId = m_modelIdArray->GetValue(i);
            if (modelVisibilityHash[modelId])
            {
                m_idArray->InsertNextValue(i);
            }
        }

        // Update the data.
        m_polyData->Modified();

        // Save the flags.
        m_modelVisibilityHash = modelVisibilityHash;
    }

    void FITKGraphObjectLoad_P::generateGraphs()
    {
        // Set the glyph size.
        m_glyphSize = m_defaultSize;

        // Create inverted arrow source.
        vtkArrowSource* arrowSource = vtkArrowSource::New();
        arrowSource->SetInvert(true);
        arrowSource->SetShaftRadius(0);
        arrowSource->SetTipLength(0.2);
        arrowSource->SetTipResolution(m_sourceResolution);
        m_sourceList.push_back(arrowSource);

        // Create actors. ( Cell centeral point load )
        generateGraph(arrowSource);
    }

    bool FITKGraphObjectLoad_P::generatePolyData(Interface::FITKAbaLoadPressure* load)
    {
        // Get the element surface of the pressure load.
        Interface::FITKMeshSurfaceElement* eleSurf = dynamic_cast<Interface::FITKMeshSurfaceElement*>(load->getComponentObj());
        if (!eleSurf)
        {
            return false;
        }

        // Get the surface id.
        int surfaceId = eleSurf->getDataObjectID();

        // Get or generate surface data.
        vtkUnstructuredGrid* surfGrid = FITKSetSurfPolyProvider::getInstance()->getSurfaceGridData(surfaceId, m_modelIds);
        if (!surfGrid)
        {
            return false;
        }

        // Save surface id.
        m_surfaceId = surfaceId;

        // Copy the model id array for both set or surface and load glyph.
        vtkDataArray* modelArray = surfGrid->GetCellData()->GetArray(FITKSetSurfPolyProvider::getInstance()->getModelIdsArrayName());
        if (modelArray)
        {
            m_modelIdArray->DeepCopy(modelArray);
        }

        // Create the cells center filter.
        vtkCellCenters* cellCenterFilter = vtkCellCenters::New();
        cellCenterFilter->SetVertexCells(true);
        cellCenterFilter->SetInputData(surfGrid);
        cellCenterFilter->Update();
        m_polyData->DeepCopy(cellCenterFilter->GetOutput());

        // Save the sub set ids.
        m_subSetIds = FITKSetSurfPolyProvider::GetSurfaceSubSetIds(surfaceId);

        return true;
    }

    void FITKGraphObjectLoad_P::generateGraph(vtkPolyDataAlgorithm* src, double size, QVariant details)
    {
        Q_UNUSED(details);

        if (!m_polyData || !src || !m_extractSelection)
        {
            return;
        }

        // Set the normals and scalars.
        // m_polyData->GetPointData()->SetNormals(m_normal);
        // m_polyData->GetPointData()->SetScalars(m_scalar);

        // Glyph.
        vtkGlyph3D* glyph = vtkGlyph3D::New();
        vtkDistanceToCamera* distanceToCamera = vtkDistanceToCamera::New();
        if (size <= 0)
        {
            distanceToCamera->SetScreenSize(m_glyphSize);
        }
        else
        {
            distanceToCamera->SetScreenSize(size);
        }

        glyph->SetInputConnection(distanceToCamera->GetOutputPort());

        // Add the cell array name need to be used.      
#if VTK_MAJOR_VERSION < 8
        glyph->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, "DistanceToCamera");
#else
        glyph->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, distanceToCamera->GetDistanceArrayName());
#endif

        glyph->SetScaling(true);
        glyph->SetScaleModeToScaleByScalar();
        glyph->SetVectorModeToUseNormal();

        // Set the soure data and input data.
        if (m_useMaskPoints)
        {
            // Initialize the mask points.
            vtkMaskPoints* maskPoints = vtkMaskPoints::New();
            FITKGraphCommons::InitializeMaskPoints(maskPoints, m_extractSelection->GetOutputPort(), m_maxSampleNumber);
            m_maskPointsList.push_back(maskPoints);

            distanceToCamera->SetInputConnection(maskPoints->GetOutputPort());
        }
        else
        {
            distanceToCamera->SetInputConnection(m_extractSelection->GetOutputPort());
        }

        glyph->SetSourceConnection(src->GetOutputPort());
        m_extractSelection->SetInputData(m_polyData);
        m_selectNode->SetFieldType(vtkSelectionNode::SelectionField::POINT);

        // Add to data list.
        m_glyphList.push_back(glyph);
        m_distToCameraList.push_back(distanceToCamera);

        // Create actor.
        FITKGraphActor* actor = FITKGraphActor::New();
        actor->setGraphObject(this);
        actor->SetPickable(false);
        actor->setInputConnection(glyph->GetOutputPort());
        addActor(actor);
        m_loadActorList.push_back(actor);

        // Initialize actor's properties.( Virtual function )
        initActorProperties(actor, m_details);
    }

    void FITKGraphObjectLoad_P::initActorProperties(vtkProp* actor, QVariant details)
    {
        Q_UNUSED(details);

        if (!actor)
        {
            return;
        }

        // Set model actor properties.
        // Default show face and edge.
        FITKGraphActor* fActor = FITKGraphActor::SafeDownCast(actor);
        if (!fActor)
        {
            return;
        }

        // Set the scalar off.
        fActor->setScalarVisibility(false);
        fActor->GetProperty()->SetLineWidth(m_glyphLineWidth);
        fActor->GetProperty()->SetRepresentation(1);

        double color3[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_pressureLoadColor, color3);
        fActor->GetProperty()->SetColor(color3);

        // setLightPropertiesForLine(fActor);
    }

    void FITKGraphObjectLoad_P::clearData()
    {
        resetVtkObj(m_polyData);

        resetVtkObj(m_normal);
        resetVtkObj(m_loadPoints);

        if (m_normal)
        {
            m_normal->SetNumberOfComponents(3);
        }

        // Reset the saved load points.
        resetVtkObj(m_loadPoints);

        // Reset the component id.
        m_setId = -1;
        m_surfaceId = -1;

        // Clear model information.
        clearModelsInfo();
    }
}   // namespace Exchange